迭代器
迭代器
迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
迭代器有两个基本的方法:iter() 和 next()。
-
iter 方法将一个自定义迭代器类对象或者一个数据容器包装成一个迭代器对象
-
next 方法从迭代器返回下一元素(第一次返回第一个元素)。所有的元素都已经迭代完了的话,再调用 next 方法会报错 StopIteration,此时如果给了默认值,则返回默认值,而不会报错。
数据容器迭代器
通过 next 方法获取迭代器中的元素:next 方法获取最后一个元素之后再调用 next,会报错 StopIteration,你也可以给一个默认值来避免报错
iter_01 = iter("abc")
print(next(iter_01))
print(next(iter_01))
print(next(iter_01))
# next方法获取最后一个元素之后再调用next,会报错 StopIteration
# StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况
# print(next(iter_01))
# 你也可以给一个默认值来避免报错
print(next(iter_01,"我靠"))
输出:
a
b
c
我靠
while + next 实现循环遍历迭代器的元素,注意处理 StopIteration 异常
但是直接使用 for 循环不会报这个错,所以我们更推荐 for 循环遍历迭代器
iter_02 = iter("abcdef")
while True:
try:
print(next(iter_02), end=" | ")
except StopIteration:
print("迭代结束")
break
输出:
a | b | c | d | e | f | 迭代结束
我们还可以迭代器他数据容器
# 迭代range对象
iter_03 = iter(range(0, 10))
for x in iter_03:
print(x, end=" | ")
print()
# 迭代元组
tuple_01 = (*range(0, 10),)
iter_04 = iter(tuple_01)
for x in iter_04:
print(x, end=" | ")
print()
# 迭代列表
iter_05 = iter([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
for x in iter_05:
print(x, end=" | ")
print()
输出:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
自定义迭代器类
数据容器之所以能够被迭代,是因为他们默认实现了 __iter__()
和 __next__()
,
-
iter() 方法返回一个特殊的迭代器对象, 这个迭代器对象实现了 next() 方法并通过 StopIteration 异常标识迭代的完成。
-
next() 方法(Python 2 里是 next())会返回下一个迭代器元素。
注意,StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况,在 next() 方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。如果不这么做,迭代器就是一个会一直返回元素的无限迭代器。
我们也可以自定义一个类来实现这两个方法,通过这种方式,自定义迭代器
class MyIterator:
def __iter__(self):
self.a = 1
return self
# def __next__(self):
# """注意,这种实现是可以无限得带下去的"""
# x = self.a
# self.a += 1
# return x
def __next__(self):
if self.a <= 10:
x = self.a
self.a += 1
return x
else:
# StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况,在 __next__() 方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。
raise StopIteration
简单迭代一下:
myIterator = MyIterator()
myiterObj = iter(myIterator)
for x in myiterObj:
print(x, end=" | ")
输出:
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
生成器 - 等同于自定义迭代器类
注意,这个概念,Java 是没有的
yield 是一个关键字,其作用是,当运行到 yield 语句时,函数的执行将会暂停,并将 yield 后面的表达式的值返回,然后当再次调用yield所在函数的时候,函数会从上次暂停的地方继续执行,直到再次遇到 yield 语句。
带有yield的函数不再是一个普通函数,Python 解释器会将其视为一个 generator(生成器),假设有一个包含 yield 关键字的函数名为 gener()
,调用 gener() 看起来像函数调用, 但不会执行 gener 函数,而是返回一个 iterable 对象!然后我们在 for 循环中遍历这个对象,在 for 循环执行时,每次循环都会执行 gener 函数内部的代码,执行到 yield 语句 时,gener 函数就返回一个迭代值,下次迭代时,代码从 yield 语句 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到直接返回,或者再次遇到 yield。
说白了,yield 的作用就是把一个 function 变成一个 generator(如果不包含 yield 关键字,类型为 function,包含了 yield 关键字,类型为 generator),包含 yield 关键字的函数,就是一个生成器函数,调用一个生成器函数,返回的是一个生成器对象,这个生成器对象同时也是一个迭代器对象。由于函数可以指定参数,因此,生成器可以完全实现前面的自定义迭代器类的效果,而且更加的简洁。
yield有点像 break 和 Java 多线程中的 await 方法的效果结合
# 包含yield关键字的函数,是一个生成器函数,一个生成器函数对象,概念上就是一个迭代器,跟迭代器的用法一样
def countdown(n):
while n > 0:
yield n
n -= 1
# 创建生成器对象
# 一个生成器对象,也是一个迭代器对象
generator = countdown(5)
# 输出 <class 'generator'>
# 如果不包含yield 关键字,类型为 function
print(type(generator))
# 通过迭代生成器获取值
print(next(generator)) # 输出: 5
print(next(generator)) # 输出: 4
print(next(generator)) # 输出: 3
# 使用 for 循环迭代生成器
for value in generator:
print(value) # 输出: 2 1
输出:
<class 'generator'>
5
4
3
2
1
在一个 generator function 中,如果没有 return,则默认执行至函数完毕,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。
# 带有return的生成器
def countdown_with_return(n):
while n > 0:
yield n
n -= 1
if n == 2:
# 使用该return则排出错误
# 抛出错误 StopIteration
return
generator_with_return = countdown_with_return(3)
# 输出3
print(next(generator_with_return))
# 报错,输出 StopIteration
# print(next(generator_with_return))
由于有 yield 关键字的方法就是生成器,没有的方法就是函数,很难快速判断,但是我们可以通过函数来判断
# 判断是不是生成器
from inspect import isgeneratorfunction
def test_function():
print("test")
# True
print(isgeneratorfunction(countdown))
# False
print(isgeneratorfunction(test_function))
# False
print(isgeneratorfunction(lambda: print()))
yield 的简单的应用,生成斐波那契数列
# 生成器本质上是一个产生迭代器的函数,作用类似于前面的自定义迭代器类
def fibonacci(n):
count, a, b = 1, 1, 2
while count <= n:
yield a
a, b = b, a + b
count += 1
fibonacci_generator = fibonacci(5)
for i in iter(fibonacci_generator):
print(i, end=" | ")
输出:
1 | 2 | 3 | 5 | 8 |
让我们回到 yield 关键字最开始的定义,我们会发现,通过 yield 关键字,我们可以把一个方法分成两段运行,第一段是从方法开始到 yield 语句,第二段是从 yield 的下一句到方法末尾,很有意思
def yield_test():
print("yield before")
# yield 返回 None
yield
print("yield after")
yield_value = yield_test()
next(yield_value)
# 给个默认值,next读取不到值的时候不会爆 StopIteration 错误
next(yield_value,"end")
输出:
yield before
yield after
跟迭代器的不同
迭代器和生成器有什么区别?
为了在结果序列长度很大的时候节约内存,我们不能一股脑将所有序列放到一个容器中,而是通过使用迭代器来按需输出结果序列,为了实现这个目的,实际上我们自定义一个迭代器类也可以实现目的,但是效果没有直接在原函数中使用 yield 函数来的简洁
yield 的好处是显而易见的,把一个函数改写为一个 generator 就获得了迭代能力,比起用一个迭代器类的实例保存状态来计算下一个 next() 的值,不仅代码简洁,而且执行流程异常清晰。
感觉只要是不太复杂的迭代器,基本上都能改写成 yield 的版本,也推荐使用 yield 的版本。